{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Game notes\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## Print Player\n",
    "\n",
    "The player is represented by a sprite 8 pixels wide and 16 pixels tall.\n",
    "\n",
    "![RunTiny player sprite](./assets/img/...)\n",
    "* The player sprite. The bitmap is 16 bits tall but only 11 are used *\n",
    "\n",
    "And this is the sprite bitmap:\n",
    "```\n",
    "player_sprite: \n",
    ".byte 0x00, 0x0e ; Column 1\n",
    ".byte 0x00, 0x18 ; Column 2\n",
    ".byte 0x00, 0xfc ; Column 3\n",
    ".byte 0x00, 0x3f ; Column 4\n",
    ".byte 0xc0, 0xff ; Column 5\n",
    ".byte 0xa0, 0x0f ; Column 6\n",
    ".byte 0xe0, 0x02 ; Column 7\n",
    ".byte 0xc0, 0x00 ; Column 8\n",
    "```\n",
    "\n",
    "To draw the player one must consider that it only moves up and down. Hence only occupies a portion of the screen that is as wide as the sprite (8 pixels) and extends vertically across the whole screen (32 pixels). The data is sent to the display one column at a time and each column is made by 32 bits.\n",
    "I first load the sprite into the lower half of a 32 bit variable split in 4 registers (8 bits each):\n",
    "```\n",
    "    ldi page1, 0x00\n",
    "    ldi page2, 0x00\n",
    "    ld  page3, Z+        ; Upper half of the sprite\n",
    "    ld  page4, Z+        ; Lower half of the sprite\n",
    "```\n",
    "Then I move the bytes right (because of the display orientation) a number of times same as the the player y position\n",
    "```\n",
    "loop_h:\n",
    "    lsr page4           ; Move one bit right for as many positions as the sprite y position \n",
    "    ror page3           ; Move right because of the display orientation\n",
    "    ror page2\n",
    "    ror page1\n",
    "dec_h:\n",
    "    dec _h              ; Loop over the player y position\n",
    "    brpl loop_h\n",
    "```\n",
    "This is the similar as doing `var >>= _h` in *c* with var being an uint32_t.\n",
    "Then I write these bytes to the display RAM and repeat for each one of the 8 columns the sprite is made of.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "## Jump\n",
    "\n",
    "![Sprite jumping](./assets/..)\n",
    "\n",
    "The game only requires one input and is used to make the player jump. The input comes from a push-button connected to PB2 of the ATtiny10\n",
    "The button press is recognized by an external interrupt routine that comes handy also to wake up the device from deep sleep.\n",
    "Two variables are used to manage the jump: one is the player vertical position and the other defines the movement direction. Let's call it vertical speed. The speed can be either a positive constant, a negative constant or zero. \n",
    "When the button is pushed the speed is set to +JUMP_SPEED (that is an arbitrary number of pixels). \n",
    "```\n",
    "INT0_vect:\n",
    "    ldi jump, JUMP_SPEED\n",
    "    reti\n",
    "```\n",
    "At each frame the player y position is increased by the vertical speed and the player goes up. When the maximum height is reached the vertical speed becomes negative and the player starts going down. When touches the ground the speed is set to zero.\n",
    "```\n",
    "; Move the player\n",
    "    add     H, jump     ; Change height by jump direction\n",
    "    cpi     H, H_MAX    ; Check if H_MAX is reached\n",
    "    brlo    .+4\n",
    "    ldi     jump, -JUMP_SPEED     ; Start falling\n",
    "    rjmp    .+6\n",
    "    tst     H           ; Check if heigh is 0\n",
    "    brne    .+2\n",
    "    ldi     jump, 0x00  ; If ground is reached speed is 0\n",
    "```\n",
    "\n",
    "\n",
    "## Update obstacles position\n",
    "\n",
    "There can be at most three obstacles in the screen. Their positions are stored in three variables in the RAM \n",
    "In each frame the obstacles position is updated decreasing it by a certain number of pixels. \n",
    "If the player jumps the obstacle it's position will eventually become negative. At that point, the variables positions 2 and 3 are moved into 1 and 2. The variable 3 is loaded with 0 meaning there are only two obstacles on screen now. \n",
    "```\n",
    "    subi    pos1, STEP         ; Move enemy position by STEP\n",
    "    brpl    .+6                ; If negative swap position 1 and 2 with 2 and 3\n",
    "    mov     pos1, pos2\n",
    "    mov     pos2, pos3\n",
    "    ldi     pos3, 0x00         ; Load position 3 with 0\n",
    "```\n",
    "\n",
    "If there are less then three enemies on screen a new one needs to be genereted.\n",
    "I do this with a sort of [Xorshift](https://en.wikipedia.org/wiki/Xorshift) random number generator where I also *xor* the current state of the RNG with the player height that can be considered a source of randomness. \n",
    "If the preudo random number generated is above a certain threshold a new obstacle is generated at end of the screen (position 120)\n",
    "\n",
    "```\n",
    "; Xorshift RNG (kind of...)\n",
    "    lds     tmp1, rand      ; Load previous random number\n",
    "    mov     pos3, tmp1      ; Copy it in a variable\n",
    "    lsl     pos3            ; Shift left by 1 position\n",
    "    eor     tmp1, pos3      ; XOR the two numbers\n",
    "    eor     tmp1, R16       ; XOR with player height that is quite random\n",
    "    sts     rand, tmp1      ; Store the new random number\n",
    "    ldi     pos3, 0x00      \n",
    "    cpi     tmp1, 190       ; If the random number is greater than 190 generate a new one\n",
    "    brlo    enemy_generated\n",
    "new_enemy:    \n",
    "    ldi     pos3, 120       ; A new enemy is generated\n",
    "```\n",
    "\n",
    "## Print the obstacles\n",
    "![Enemy Sprite](img)\n",
    "* - Very dangerous bush - *\n",
    "\n",
    "Printing the obstacles is done updating the last page of the display. I loop over the entire screen width from the column 0 and compare the current column with the obstacles position. If these are equal print the display, otherwise the column is empty.\n",
    "```\n",
    "    ld      x_pos, Y+           ; Load obstacle position\n",
    "loop_comlumns:\n",
    "    cp      col_cnt, x_pos      ; Compare enemy position with current column drawn\n",
    "    brne    blank_col           \n",
    "    ldi     ZL, lo8(sprite_obstacle+0x4000) ; Load sprite position pointer\n",
    "    ldi     ZH, hi8(sprite_obstacle+0x4000)\n",
    "    ldi     sprite_w, 5         ; Sprite width -1\n",
    "print_sprite:\n",
    "    ld      R24, Z+             ; Load sprite byte\n",
    "    rcall   Tx                  ; Send sprite byte\n",
    "    inc     col_cnt             ; Increase column count \n",
    "    dec     sprite_w            ; Increase sprite byte count \n",
    "    brpl    print_sprite        ; Check if the entire sprite is printed\n",
    "    ld      x_pos, Y+           ; Next Obstacle position\n",
    "blank_col:\n",
    "    ldi     R24, 0x80           ; Lower bit is set to draw the ground\n",
    "    rcall   Tx                  ; Send data to display RAM\n",
    "    inc     col_cnt             ; Increase column count \n",
    "    cpi     col_cnt, 128        ; Check if the whole display width is printed\n",
    "    brne    loop_comlumns \n",
    "```\n",
    "\n",
    "In *c* this code would be something similar to this (not tested):\n",
    "```\n",
    "uint8_t* x_pos = &obstacles;\n",
    "uint8_t col_cnt = 0;\n",
    "uint8_t i=0;\n",
    "\n",
    "do{\n",
    "    if(col_cnt == *x_pos){\n",
    "        do{\n",
    "            Tx(sprite[i++]);\n",
    "            col_cnt++;\n",
    "        }while(i<sprite_width);\n",
    "        i=0;\n",
    "        x_pos++;\n",
    "    }\n",
    "    Tx(0);\n",
    "    col_cnt++;\n",
    "}while(col_cnt<128);\n",
    "\n",
    "```\n",
    "\n",
    "\n",
    "## Score\n",
    "To have a score you need to store the number itself and translate it in something that can be printed on screen.\n",
    "At first, my thought was to keep the score in a variable. This poses two issues. First, to have a score higher than 255 you need to use more than a 8-bit variable. With an 8-bit CPU this means more instructions and in turn more memory space. Second, how do you get each single digit from a binary number?\n",
    "Let's imagine you want to get the tens digit. One obvious way is to divide by 10 and then do modulus ten of the result: `digit = (score/10)%10`. The problem is that this is very demanding for a processor that can't do divisions. \n",
    "However, I observed that the score only increases starting from zero. This means that I don't need to know the number but just sequentially print the digits.\n",
    "To understand better consider that the digit font is stored in an array of 5x10 bytes where 5 is the pixels width of the font.\n",
    "This works with three variables each storing the position of the current decimal digit within the array. Each time the score increases the pointer of the units moves to the next digit position (i.e. from 0 to 5, for a font 5 pixel wide). When it reaches the end of the array, goes back to 0 and the variable of the tens moves by one digit.\n",
    "So the score is actually not stored as a number but just as a reference.\n",
    "\n",
    "```\n",
    "print_score:\n",
    "    lds     R23, score_digit_1      ; Load units position from memory\n",
    "    subi    R23, (-W)               ; Increase position by font width \n",
    "    cpi     R23, (W *10)            ; Check if the end is reached\n",
    "    brlo    store+2                 ; If not, stor the updated variable\n",
    "    ldi     R23, 0                  ; If 9 has passed restart from 0\n",
    "    lds     R24, score_digit_2      ; Load tens position from memory\n",
    "    subi    R24, (-W)               ; Increase tens by width\n",
    "    cpi     R24, (W *10)            ; Check if the end is reached\n",
    "    brlo    store                   ; If not, stor the updated variable\n",
    "    ldi     R24, 0                  ; If 9 has passed restart from 0 \n",
    "    lds     R25, score_digit_3      ; Increase hundreds by font width\n",
    "    subi    R25, (-W)               ; Increase hundreds by font width\n",
    "    sts     score_digit_3, R25      ; Store hundreds position into memory\n",
    "store:\n",
    "    sts     score_digit_2, R24      ; Store tens position into memory\n",
    "    sts     score_digit_1, R23      ; Store units position into memory\n",
    "```\n",
    "\n",
    "This is somewhat similar to the *c* code:\n",
    "```\n",
    "score_digit_1 += W;\n",
    "if(score_digit_1 = W*10){\n",
    "    score_digit_1 = 0;\n",
    "    score_digit_2 += W;\n",
    "    if(score_digit_1 = W*10){\n",
    "        score_digit_2 = 0;\n",
    "        score_digit_3 += W;\n",
    "    }\n",
    "}\n",
    "```\n",
    "\n",
    "\n",
    "## Game over, sleep and reset\n",
    "In each cycle I check if the first enemy is behind the player or in front of him. In this case the player position doesn't care. Instead, if the obstacle is in the space occupied by the player width, check the player vertical position. If it's higher than the obstacle, good! Loop again. Otherwise *GAME OVER*.\n",
    "```\n",
    "; Check if GAME OVER\n",
    "    lds     enemy_x, enemy_pos \n",
    "    cpi     enemy_x, 16     ; Enemy position higher than player position. Don't care player height\n",
    "    brsh    infinite_loop\n",
    "    cpi     enemy_x, 10     ; Enemy has passed. Don't care player height\n",
    "    brlo    infinite_loop\n",
    "    cpi     H, 8            ; Otherwise check if player is above enemy\n",
    "    brsh    infinite_loop\n",
    "```\n",
    "At game over I invert the display RAM (all dark pixels are lit and vice versa) a couple of times. After that I switch off the display and set the microcontroller to sleep.\n",
    "\n",
    "To wake up the system and start a new game you need to press the button; everything is reset and you can play again.\n",
    "\n",
    "\n",
    "## Set the frame rate\n",
    "Let's call it frame rate as it sounds cool but is essentially a delay between one image and another (oh wait... but this is the frame rate definition). \n",
    "At the beginning of every loop the program waits for a number of milliseconds. \n",
    "How can we pause the microcontroller for a certain time? We can make it count. Since the clock frequency is 1MHz, one clock cycle will take 1us. Therefore 1,000 clock cycles will take 1ms. If we know how many clock cycles a loop takes, we can calculate the number of iterations required for a certain execution time.\n",
    "In the following code the number of iterations is passed through registers `R24` and `R25`. The loop just decrease these registers by one. Each iteration takes 4 cycles therefore 4us. A delay of 1 millisecond will be given by 250 iterations. [----- Notes: to be pedantic the and have a precise delay we should take into account also the return that takes 4 clock cycles. So the iterations should be one less]  \n",
    "\n",
    "``` assembly\n",
    "; Delay routine \n",
    "; Input:    Number of iterations: 16-bit in R24 and R25\n",
    ";           Each iteration takes 4us (0.004ms)\n",
    ";           Return takes 4us\n",
    "_delay: \n",
    "    subi\tR24, 0x01\t\n",
    "    sbci\tR25, 0x00\n",
    "    brne\t.-6\n",
    "ret\n",
    "```\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
